"""
FFmpeg视频处理器

基于FFmpeg的视频切割、转码和处理功能。
"""

import asyncio
import subprocess
import json
import os
from typing import List, Dict, Any, Optional, Tuple
import logging

from src.core.di import Injectable, Inject
from .base import VideoProcessor


@Injectable(scope="singleton")
class FFmpegProcessor(VideoProcessor):
    """
    FFmpeg视频处理器
    
    使用FFmpeg进行视频切割、转码和基础处理。
    """
    
    def __init__(self, 
                 config: Dict[str, Any] = Inject("config"),
                 logger: logging.Logger = Inject("logger")):
        super().__init__()
        self.config = config
        self.logger = logger
        
        # FFmpeg配置
        self.ffmpeg_path = self._get_ffmpeg_path()
        self.ffprobe_path = self._get_ffprobe_path()
        
        # 默认参数
        self.default_video_codec = "libx264"
        self.default_audio_codec = "aac"
        self.default_quality = "medium"
        
        # 质量预设
        self.quality_presets = {
            "low": {
                "video_bitrate": "500k",
                "audio_bitrate": "64k",
                "scale": "640:360",
                "crf": "28"
            },
            "medium": {
                "video_bitrate": "1500k", 
                "audio_bitrate": "128k",
                "scale": "1280:720",
                "crf": "23"
            },
            "high": {
                "video_bitrate": "3000k",
                "audio_bitrate": "192k", 
                "scale": "1920:1080",
                "crf": "18"
            }
        }
    
    def _get_ffmpeg_path(self) -> str:
        """获取FFmpeg可执行文件路径"""
        # 从配置获取
        ffmpeg_path = self.config.get("ffmpeg", {}).get("binary_path", "ffmpeg")
        
        # 检查是否可执行
        try:
            subprocess.run([ffmpeg_path, "-version"], 
                         capture_output=True, check=True, timeout=5)
            return ffmpeg_path
        except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
            # 尝试从PATH查找
            import shutil
            path = shutil.which("ffmpeg")
            if path:
                return path
            
            raise RuntimeError("未找到FFmpeg可执行文件，请安装FFmpeg或配置正确路径")
    
    def _get_ffprobe_path(self) -> str:
        """获取FFprobe可执行文件路径"""
        ffprobe_path = self.config.get("ffmpeg", {}).get("ffprobe_path", "ffprobe")
        
        try:
            subprocess.run([ffprobe_path, "-version"],
                         capture_output=True, check=True, timeout=5)
            return ffprobe_path
        except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
            import shutil
            path = shutil.which("ffprobe")
            if path:
                return path
            
            raise RuntimeError("未找到FFprobe可执行文件，请安装FFmpeg或配置正确路径")
    
    async def slice_video(self, 
                         input_path: str,
                         output_dir: str,
                         max_duration: int = 30,
                         min_duration: int = 3,
                         quality: str = "medium",
                         scene_detection: bool = True) -> List[str]:
        """
        切割视频
        
        Args:
            input_path: 输入视频路径
            output_dir: 输出目录
            max_duration: 最大切片时长（秒）
            min_duration: 最小切片时长（秒）
            quality: 输出质量
            scene_detection: 是否启用场景检测
        
        Returns:
            切片文件路径列表
        """
        self.logger.info(f"开始切割视频: {input_path}")
        
        # 创建输出目录
        os.makedirs(output_dir, exist_ok=True)
        
        # 获取视频信息
        video_info = await self.get_video_info(input_path)
        total_duration = video_info.get("duration", 0)
        
        if total_duration <= max_duration:
            # 视频时长小于最大切片时长，直接转码
            output_path = os.path.join(output_dir, "slice_001.mp4")
            await self.transcode_video(input_path, output_path, quality)
            return [output_path]
        
        # 生成切片时间点
        if scene_detection:
            slice_points = await self._detect_scene_changes(input_path, max_duration, min_duration)
        else:
            slice_points = self._generate_fixed_slices(total_duration, max_duration)
        
        # 执行切片
        slice_files = []
        for i, (start_time, end_time) in enumerate(slice_points):
            output_path = os.path.join(output_dir, f"slice_{i+1:03d}.mp4")
            
            await self._extract_segment(
                input_path, output_path, 
                start_time, end_time, quality
            )
            
            slice_files.append(output_path)
        
        self.logger.info(f"视频切割完成，生成 {len(slice_files)} 个切片")
        return slice_files
    
    async def _detect_scene_changes(self, 
                                   input_path: str,
                                   max_duration: int,
                                   min_duration: int) -> List[Tuple[float, float]]:
        """检测场景变化并生成切片时间点"""
        
        # 使用FFmpeg的场景检测滤镜
        cmd = [
            self.ffmpeg_path,
            "-i", input_path,
            "-vf", "select='gt(scene,0.3)',showinfo",
            "-f", "null", "-"
        ]
        
        try:
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            
            stdout, stderr = await process.communicate()
            
            # 解析场景变化时间点
            scene_times = self._parse_scene_times(stderr.decode())
            
            # 生成切片区间
            return self._generate_slices_from_scenes(scene_times, max_duration, min_duration)
            
        except Exception as e:
            self.logger.warning(f"场景检测失败，使用固定切片: {e}")
            # 回退到固定切片
            video_info = await self.get_video_info(input_path)
            total_duration = video_info.get("duration", 0)
            return self._generate_fixed_slices(total_duration, max_duration)
    
    def _parse_scene_times(self, stderr_output: str) -> List[float]:
        """解析FFmpeg场景检测输出"""
        scene_times = []
        
        for line in stderr_output.split('\n'):
            if 'showinfo' in line and 'pts_time:' in line:
                try:
                    # 提取时间戳
                    pts_time_start = line.find('pts_time:') + 9
                    pts_time_end = line.find(' ', pts_time_start)
                    if pts_time_end == -1:
                        pts_time_end = len(line)
                    
                    time_str = line[pts_time_start:pts_time_end]
                    scene_time = float(time_str)
                    scene_times.append(scene_time)
                except (ValueError, IndexError):
                    continue
        
        return sorted(scene_times)
    
    def _generate_slices_from_scenes(self, 
                                   scene_times: List[float],
                                   max_duration: int,
                                   min_duration: int) -> List[Tuple[float, float]]:
        """根据场景时间点生成切片区间"""
        if not scene_times:
            return []
        
        slices = []
        start_time = 0.0
        
        for scene_time in scene_times:
            duration = scene_time - start_time
            
            if duration >= min_duration:
                if duration <= max_duration:
                    # 合适的切片长度
                    slices.append((start_time, scene_time))
                    start_time = scene_time
                else:
                    # 切片过长，需要进一步分割
                    while start_time + max_duration < scene_time:
                        slices.append((start_time, start_time + max_duration))
                        start_time += max_duration
                    
                    # 剩余部分
                    if scene_time - start_time >= min_duration:
                        slices.append((start_time, scene_time))
                        start_time = scene_time
        
        return slices
    
    def _generate_fixed_slices(self, total_duration: float, max_duration: int) -> List[Tuple[float, float]]:
        """生成固定时长的切片区间"""
        slices = []
        start_time = 0.0
        
        while start_time < total_duration:
            end_time = min(start_time + max_duration, total_duration)
            slices.append((start_time, end_time))
            start_time = end_time
        
        return slices

    async def _has_audio_stream(self, input_path: str) -> bool:
        """检测视频是否包含音轨"""
        try:
            cmd = [
                self.ffprobe_path,
                "-v", "quiet",
                "-select_streams", "a",
                "-show_entries", "stream=codec_type",
                "-of", "csv=p=0",
                input_path
            ]

            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )

            stdout, stderr = await process.communicate()

            if process.returncode == 0:
                output = stdout.decode().strip()
                return bool(output)  # 如果有音轨，输出不为空
            else:
                self.logger.warning(f"检测音轨失败: {stderr.decode()}")
                return False

        except Exception as e:
            self.logger.warning(f"检测音轨时出错: {e}")
            return False

    async def _extract_segment(self,
                              input_path: str,
                              output_path: str,
                              start_time: float,
                              end_time: float,
                              quality: str) -> None:
        """提取视频片段"""

        preset = self.quality_presets.get(quality, self.quality_presets["medium"])

        # 检测是否有音轨
        has_audio = await self._has_audio_stream(input_path)

        # 构建基础命令
        cmd = [
            self.ffmpeg_path,
            "-i", input_path,
            "-ss", str(start_time),
            "-t", str(end_time - start_time),
            "-c:v", self.default_video_codec,
            "-b:v", preset["video_bitrate"],
            "-crf", preset["crf"]
        ]

        # 只有当视频有音轨时才添加音频参数
        if has_audio:
            cmd.extend([
                "-c:a", self.default_audio_codec,
                "-b:a", preset["audio_bitrate"]
            ])
        else:
            # 明确指定不处理音频
            cmd.append("-an")

        # 添加输出参数
        cmd.extend([
            "-y",  # 覆盖输出文件
            output_path
        ])
        
        process = await asyncio.create_subprocess_exec(
            *cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )
        
        stdout, stderr = await process.communicate()
        
        if process.returncode != 0:
            error_msg = stderr.decode() if stderr else "未知错误"
            raise RuntimeError(f"FFmpeg切片失败: {error_msg}")
    
    async def transcode_video(self,
                             input_path: str,
                             output_path: str,
                             quality: str = "medium",
                             scale: Optional[str] = None) -> None:
        """
        转码视频
        
        Args:
            input_path: 输入视频路径
            output_path: 输出视频路径
            quality: 输出质量
            scale: 缩放尺寸 (如 "1280:720")
        """
        
        preset = self.quality_presets.get(quality, self.quality_presets["medium"])

        # 检测是否有音轨
        has_audio = await self._has_audio_stream(input_path)

        # 构建基础命令
        cmd = [
            self.ffmpeg_path,
            "-i", input_path,
            "-c:v", self.default_video_codec,
            "-b:v", preset["video_bitrate"],
            "-crf", preset["crf"]
        ]

        # 只有当视频有音轨时才添加音频参数
        if has_audio:
            cmd.extend([
                "-c:a", self.default_audio_codec,
                "-b:a", preset["audio_bitrate"]
            ])
        else:
            # 明确指定不处理音频
            cmd.append("-an")

        # 添加缩放滤镜
        if scale or preset.get("scale"):
            scale_value = scale or preset["scale"]
            cmd.extend(["-vf", f"scale={scale_value}"])

        cmd.extend(["-y", output_path])
        
        self.logger.info(f"开始转码视频: {input_path} -> {output_path}")
        
        process = await asyncio.create_subprocess_exec(
            *cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )
        
        stdout, stderr = await process.communicate()
        
        if process.returncode != 0:
            error_msg = stderr.decode() if stderr else "未知错误"
            raise RuntimeError(f"FFmpeg转码失败: {error_msg}")
        
        self.logger.info(f"视频转码完成: {output_path}")
    
    async def get_video_info(self, video_path: str) -> Dict[str, Any]:
        """
        获取视频信息
        
        Args:
            video_path: 视频文件路径
        
        Returns:
            视频信息字典
        """
        
        cmd = [
            self.ffprobe_path,
            "-v", "quiet",
            "-print_format", "json",
            "-show_format",
            "-show_streams",
            video_path
        ]
        
        process = await asyncio.create_subprocess_exec(
            *cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )
        
        stdout, stderr = await process.communicate()
        
        if process.returncode != 0:
            error_msg = stderr.decode() if stderr else "未知错误"
            raise RuntimeError(f"FFprobe获取视频信息失败: {error_msg}")
        
        try:
            probe_data = json.loads(stdout.decode())
            return self._parse_video_info(probe_data)
        except json.JSONDecodeError as e:
            raise RuntimeError(f"解析FFprobe输出失败: {e}")
    
    def _parse_video_info(self, probe_data: Dict[str, Any]) -> Dict[str, Any]:
        """解析FFprobe输出的视频信息"""
        
        format_info = probe_data.get("format", {})
        streams = probe_data.get("streams", [])
        
        # 查找视频流
        video_stream = None
        audio_stream = None
        
        for stream in streams:
            if stream.get("codec_type") == "video":
                video_stream = stream
            elif stream.get("codec_type") == "audio":
                audio_stream = stream
        
        info = {
            "filename": format_info.get("filename", ""),
            "duration": float(format_info.get("duration", 0)),
            "size": int(format_info.get("size", 0)),
            "bitrate": int(format_info.get("bit_rate", 0)),
            "format_name": format_info.get("format_name", ""),
        }
        
        if video_stream:
            info.update({
                "width": int(video_stream.get("width", 0)),
                "height": int(video_stream.get("height", 0)),
                "fps": self._parse_fps(video_stream.get("r_frame_rate", "0/1")),
                "video_codec": video_stream.get("codec_name", ""),
                "video_bitrate": int(video_stream.get("bit_rate", 0)),
            })
        
        if audio_stream:
            info.update({
                "audio_codec": audio_stream.get("codec_name", ""),
                "audio_bitrate": int(audio_stream.get("bit_rate", 0)),
                "sample_rate": int(audio_stream.get("sample_rate", 0)),
                "channels": int(audio_stream.get("channels", 0)),
            })
        
        return info
    
    def _parse_fps(self, fps_str: str) -> float:
        """解析帧率字符串"""
        try:
            if "/" in fps_str:
                numerator, denominator = fps_str.split("/")
                return float(numerator) / float(denominator)
            else:
                return float(fps_str)
        except (ValueError, ZeroDivisionError):
            return 0.0
    
    async def extract_thumbnails(self,
                                video_path: str,
                                output_dir: str,
                                count: int = 3,
                                quality: str = "medium") -> List[str]:
        """
        提取视频缩略图
        
        Args:
            video_path: 视频文件路径
            output_dir: 输出目录
            count: 缩略图数量
            quality: 图片质量
        
        Returns:
            缩略图文件路径列表
        """
        
        os.makedirs(output_dir, exist_ok=True)
        
        # 获取视频时长
        video_info = await self.get_video_info(video_path)
        duration = video_info.get("duration", 0)
        
        if duration <= 0:
            raise ValueError("无法获取视频时长")
        
        # 计算缩略图时间点
        time_points = []
        if count == 1:
            time_points = [duration / 2]
        else:
            interval = duration / (count + 1)
            time_points = [interval * (i + 1) for i in range(count)]
        
        # 提取缩略图
        thumbnail_files = []
        for i, time_point in enumerate(time_points):
            output_path = os.path.join(output_dir, f"thumbnail_{i+1:03d}.jpg")
            
            cmd = [
                self.ffmpeg_path,
                "-i", video_path,
                "-ss", str(time_point),
                "-vframes", "1",
                "-q:v", "2" if quality == "high" else "5",
                "-y",
                output_path
            ]
            
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            
            await process.communicate()
            
            if process.returncode == 0 and os.path.exists(output_path):
                thumbnail_files.append(output_path)
        
        return thumbnail_files
